
package SSF.OS.TCP.test.spoofTest;


import SSF.OS.*;
import SSF.OS.Socket.*;
import SSF.OS.TCP.test.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import SSF.Util.Random.*;
import com.renesys.raceway.DML.*;
import java.util.*;
import SSF.Util.Random.RandomStream;

/** A simple prototype class for creating TCP client-server sessions
 *  for one or more simple file transfers (virtual data bytes).
 *  Obtains DML configuration specifying available servers and
 *  session parameters, chooses server(s) randomly from the list
 *  specified in the <TT>.Net.traffic</TT> DML attribute.
 *  The client-server protocol is absolutely minimal: the client sends to server
 *  one integer that specifies the total amount of
 *  virtual data bytes it requests.
 */
public class tcpSpoofClient extends ProtocolSession {

  /************************* Class Variables *********************/

  /** option to print out debug information (from DML) */
  public boolean debug = false;

  /** option to print out summary session report (from DML) */
  public boolean show_report = false;

  /** default start time after init() from DML */
  public long start_time = SSF.Net.Net.seconds(1.0);

  /** default start time window from DML */
  public long start_window = 0L;

  /** random number generator given to this tcpSpoofClient */
  public RandomStream rng;

  /** size (in bytes) of request segment sent from client to server (from DML) */
  public int request_size;

  /** constant requested file size (from DML) */
  public int file_size = 0;

 /** Distribution of random file sizes */
  protected RandomDistribution fileSizeDistr = null;

  /** constant off-time between file requests. Default is "infinite"
   *  for a single file transfer.
   */
  public double offTime = Double.MAX_VALUE;

  /** Distribution of random off-times */
  protected RandomDistribution offTimeDistr = null;

  /** Host entity where this pseudo-protocol is installed */
  public Host localHost;

  /** Host's IP address */
  public int localIP;

  /** Host's NHI address */
  public String localNHI;

  /** Host's Socket ProtocolSession */
  public socketMaster sockms;

  /** inner class specifying a server by its IP address and port number */
  public class serverData {
    public String nhi;
    public int ip;
    public int port;

    public serverData(String server_nhi, int server_port){
      nhi = server_nhi;
      ip = IP_s.StrToInt(localHost.global_nhi_to_ip(nhi));
      port = server_port;
    }
  }

  /** Name of the server list */
  public String serverList;

  /** Servers known to this host */
  public Vector serverVector = new Vector(1,5);

  /** session number */
  public int sessNumber = 0;

  /** sleep time (in seconds)  between invocations of startOnOff() */
  public double sleepTime;

  /************************* Constructors ************************/

  public tcpSpoofClient () {}

  /************************* Class Methods ***********************/

  /** Kick-off timer to start client operation, user should
   *  comment out either the startOnce or startOnOff mode for desired behavior.
   */ 
  public void init() throws ProtocolException {
    IP ipsess;

    try {
      sockms = (socketMaster)inGraph().SessionForName("socket");
    } catch (ProtocolException e) { 
      System.err.println(e);
    }

    // changed for multiple instances of tcpSpoofClient on the same host
    // generates distinct seed strings if reproducibility_level = distribution,
    // at coarser reproducibility_levels all tcpSpoofClient instances in a
    // single host draw random numbers from a common stream.
    rng = Net.accessRandomStream(this, this.name);

    // need the local IP address
    try {
      ipsess = (IP)localHost.SessionForName("ip");
      localIP = (int)((NIC)ipsess.INTERFACES.elementAt(0)).ipAddr;
    } catch (ProtocolException e) { 
      System.err.println(e);
    }

    if(debug)
      clientDebug(rng.getName());

    // Randomize the start time among all Hosts running tcpSpoofClient
    start_time += (long)(rng.nextDouble()*start_window);
    (new SSF.OS.Timer(localHost, start_time){
      public void callback() {
        //startOnce(1);
        startOnOff();
        cancel();
      }
    }).set(start_time);
  }

  /** A simple file transfer renewal process:
   *  open a session to a randomly selected server, proceed until
   *  session is completed either with success or failure;
   *  then wait for a specified time; and repeat.
   *  Further elaborated by specifying the distributions
   *  of off-times and requested file sizes.
   */
  public void startOnOff() {
    serverData srv;
    tcpSpoofClientSession sess;

    if(serverVector.isEmpty())
      return;

    ++sessNumber;
    srv = (serverData)getRandomVectorElement(serverVector, rng);
    int fsize = file_size;
    if (fileSizeDistr != null)
	fsize = Math.max(1, fileSizeDistr.nextInt());

    sess = new tcpSpoofClientSession(this, sessNumber, srv, fsize);

    sess.begin(new Continuation() {
      public void success() {

        // start a timer for the next session if so configured
        if(offTime < Double.MAX_VALUE || offTimeDistr != null) {
          double sleeptime = offTime;
	  if (offTimeDistr != null)
	    sleeptime = offTimeDistr.nextDouble();
	
	  if (debug)
	    clientDebug("sleep " + sleeptime);
	
          (new SSF.OS.Timer(localHost, SSF.Net.Net.seconds(sleeptime)){
            public void callback() {
              startOnOff();
              cancel();
            }
          }).set(SSF.Net.Net.seconds(sleeptime));
        }
      }
      public void failure(int errno) { }
    });
  }

  public Object getRandomVectorElement(Vector v, RandomStream rng){
    int n = v.size();
    if( n != 0) {
      n = (int)(rng.nextDouble()*n);
      return v.elementAt(n);
    }
    return null;
  }

  /************************* Configuration ***********************/

  /** Configure the client instance parameters. Supported DML attributes are:
   *  <PRE>
   *  ProtocolSession [ name client use SSF.OS.TCP.test.tcpSpoofClient
   *    start_time   %F       # earliest time of sending request to server, in seconds
   *    start_window %F       # sends request to server at randomly chosen time
   *                          # in interval [start_time, start_time+start_window]
   *
   *    file_size    %I       # requested file size (number of virtual bytes, int)
   *    request_size %I       # nominal size of client request datagram (bytes, int)
   *                          # (must be the same for the server)
   *
   *    show_report  %S       # print client-server session summary report, true/false
   *    debug        %S       # print verbose client diagnostics, true/false
   *  ]
   * </PRE>
  */
  public void config(Configuration cfg) throws configException {
    super.config(cfg);
    localHost = (SSF.Net.Host)inGraph();
    localNHI = localHost.nhi;
    String strTarget;

    if((strTarget = (String)cfg.findSingle("start_time")) != null)
      start_time = SSF.Net.Net.seconds((new Float(strTarget)).floatValue());

    if((strTarget = (String)cfg.findSingle("start_window")) != null)
      start_window = SSF.Net.Net.seconds((new Double(strTarget)).doubleValue());

    if((strTarget = (String)cfg.findSingle("request_size")) != null)
      request_size = (new Integer(strTarget)).intValue();

    if((strTarget = (String)cfg.findSingle("file_size")) != null)
      file_size = (new Integer(strTarget)).intValue();

    Configuration fcfg = (Configuration) cfg.findSingle("random_file_size");
    if(fcfg != null) {
      Configuration dcfg = (Configuration)fcfg.findSingle("distribution");
      if(dcfg == null)
        throw new configException("random_file_size.distribution not found");
      fileSizeDistr =
            Net.accessRandomDistribution(this, dcfg, "random_file_size");
    }

    if (file_size > 0 && fileSizeDistr != null) {
      clientDebug("can't set both fixed and random file size");
      throw new configException("can't set both fixed and random file_size");
    }

    if((strTarget = (String)cfg.findSingle("off_time")) != null)
      offTime = (new Double(strTarget)).doubleValue();

    Configuration ocfg = (Configuration) cfg.findSingle("random_off_time");
    if(ocfg != null) {
      Configuration dcfg = (Configuration)ocfg.findSingle("distribution");
      if(dcfg == null)
        throw new configException("random_off_time.distribution not found");
      offTimeDistr =
           Net.accessRandomDistribution(this, dcfg, "random_off_time");
    }

    if (offTime < Double.MAX_VALUE && offTimeDistr != null) {
      clientDebug("can't set both fixed and random off_time");
      throw new configException("can't set both fixed and random off_time");
    }

    if((strTarget = (String)cfg.findSingle("server_list")) != null)
      serverList = strTarget;

    if((strTarget = (String)cfg.findSingle("show_report")) != null)
      if(strTarget.compareTo("true") == 0)
        show_report = true;

    if((strTarget = (String)cfg.findSingle("debug")) != null)
      if(strTarget.compareTo("true") == 0)
        debug = true;

    Configuration traffic = (Configuration)cfg.findSingle(".Net.traffic");
    if(traffic == null)
      throw new configException
        (".Net.traffic not found");
    configTraffic(traffic, localNHI, serverVector);
  }

  /** Add a list of servers available to the client(s) to the server_list.
   *  A client may match multiple NHI prefixes; the servers 
   *  specified by multiple matches are simply added to the server_list
   *  without checking for duplication.
   *  All NHI addresses must be global.
   *  A given client_nhi matches the value of the attribute "client foo'
   *  iff the NHI string "client_nhi" extends the NHI value "foo", viz. 
   *  client_nhi = foo:* or client_nhi = foo.
   *
   *  The attribute traffic can have the forms
   *<pre>
   *  traffic [
   *    pattern [
   *      client 1:1        # value N or N:H allowed, where N is NHI network prefix
   *      servers [nhi 1:2(0) port 10]  # single server
   *    ]
   *    pattern [
   *      client 2:1
   *      servers [nhi_range [from 2:2(0) to 2:5(0)] port 11] # vector of servers
   *    ]
   *  ]
   *</pre>
   *
   *  In case of a range of nhi addreses, an allowed range is of the form
   *  from N:h1(i) to N:h2(i), where N is a valid Net prefix, 
   *  and h1 <= h2 is a range of hosts.
   *
   *  In the future a generalization of this method presumably should
   *  be executed by SSF.Net.Net to avoid repetition of the same work by all
   *  tcpSpoofClients.
   */
  public void configTraffic(Configuration traffic, String client_nhi, Vector server_list)
    throws configException {
    String str;
    Configuration srv;
    int port;
    if(traffic == null) return;

    for (Enumeration patterns = traffic.find("pattern");
         patterns.hasMoreElements();) 
    {
      Configuration pattern = (Configuration)patterns.nextElement();
      str = (String)pattern.findSingle("client");
      if(!nhi_matches(str, client_nhi))
        continue;

      for (Enumeration servers = pattern.find("servers"); servers.hasMoreElements();) {
        srv = (Configuration)servers.nextElement();

        if((serverList != null) &&
           (str = (String)srv.findSingle("list")) !=null) {
          clientDebug("configTraffic: serverList= "+serverList
                       +" servers.list= "+str);
          if(!(serverList.compareTo(str) == 0))
            continue;
        }

        if((str = (String)srv.findSingle("port")) == null)
          throw new configException
            ("traffic.pattern.servers.port missing: " + servers);
        port = (new Integer(str)).intValue();

        // case of a single nhi server address
        if((str = (String)srv.findSingle("nhi")) !=null) {
          server_list.addElement(new serverData(str, port));
          continue;
        }

        // case of a range of nhi addreses. An allowed range is of the form
        // from N:h1(i) to N:h2(i), where N is a valid Net prefix, 
        // and h1 <= h2 is a range of hosts.
        Configuration nhi = (Configuration)srv.findSingle("nhi_range");
        if(nhi == null)
          throw new configException
            ("traffic.pattern.servers must have either nhi or nhi_range: " + servers);

        String nhi_min = (String)nhi.findSingle("from");
        String nhi_max = (String)nhi.findSingle("to");
        String net_min = nhi_min.substring(0,nhi_min.lastIndexOf(":")+1);
        String net_max = nhi_max.substring(0,nhi_max.lastIndexOf(":")+1);
        String host_min = nhi_min.substring(nhi_min.lastIndexOf(":")+1,
                          nhi_min.indexOf("("));
        String host_max = nhi_max.substring(nhi_max.lastIndexOf(":")+1,
                          nhi_max.indexOf("("));
        String iface_min = nhi_min.substring(nhi_min.indexOf("("),
                           nhi_min.indexOf(")")+1);
        String iface_max = nhi_max.substring(nhi_max.indexOf("("),
                           nhi_max.indexOf(")")+1);

        if (!net_min.equals(net_max))
          throw new configException
            ("traffic.pattern.servers.nhi_range: all network prefixes must be equal");

        if (!iface_min.equals(iface_max))
          throw new configException
            ("traffic.pattern.servers.nhi_range: all interface IDs must be equal");

        int hid_min = (new Integer(host_min)).intValue();
        int hid_max = (new Integer(host_max)).intValue();
        if (hid_min>hid_max)
          throw new configException
            ("traffic.pattern.servers.nhi_range: Host IDs must be ascending");

        for (int hid = hid_min; hid<=hid_max; hid++) {
          String host_nhi = net_min+hid+iface_min;
          serverVector.addElement(new serverData(host_nhi, port));
          clientDebug("configTraffic: adding server "+srv.toString());
        }
      }
    }
  }

  /** Determine whether the NHI address 'prefix' matches the NHI address 'candidate'. 
   *  This is true iff the NHI address 'candidate' extends the NHI address 
   *  'prefix': candidate = prefix:* or candidate = prefix.
   */
  private boolean nhi_matches(String prefix, String candidate) {
    if (!candidate.startsWith(prefix)) return false;
    if (candidate.length()>prefix.length() && 
        candidate.charAt(prefix.length()) != ':')
      return false;
    return true;
  }

  /** Does nothing */
  public boolean push(ProtocolMessage message, ProtocolSession fromSession)
    throws ProtocolException {
    return false;
  }

  /** preamble to client diagnostics */
  public void clientDebug(String str){
    System.err.println(localHost.now()/(double)SSF.Net.Net.seconds(1.0)
                       + " "+super.name+" " + localNHI
                       + " " + IP_s.IPtoString(localIP)
                       + " " + str);
  }
}
